import bezier from 'bezier-easing';

export const ease = bezier(0.25, 1, 0.25, 1);
export const easeIn = bezier(0.42, 0, 1, 1);
export const easeOut = bezier(0, 0, 0.58, 1);
export const easeInOut = bezier(0.42, 0, 0.58, 1);

export enum State {
  'Initialized' = 'Initialized',
  'Started' = 'Started',
  'Paused' = 'Paused',
}

export class Timeline {
  #animations: Set<Animation> = new Set();
  #startTimes: Map<Animation, number> = new Map();

  #tickHandler: ReturnType<typeof requestAnimationFrame> = -1;

  #tick = () => {
    this.#tickHandler = requestAnimationFrame(this.#tick);
  }

  #pauseStart: number = 0;
  #pauseTime: number = 0;

  #state = State.Initialized;

  start() {
    if (this.#state !== State.Initialized) {
      return;
    }
    this.#state = State.Started;

    const startTime = Date.now();
    this.#pauseTime = 0;

    this.#tick = () => {
      const now = Date.now();

      for (const animation of this.#animations) {
        const animationStartTime = Math.max(startTime, this.#startTimes.get(animation)!);
        let t = now - animationStartTime - this.#pauseTime - animation.delay;

        if (animation.duration < t) {
          this.#animations.delete(animation);

          // 保证属性最后的值不超出范围
          t = animation.duration;
        }

        if (t > 0) {
          animation.receiveTime(t);
        }
      }

      this.#tickHandler = requestAnimationFrame(this.#tick);
    }
    this.#tick();
  }

  pause() {
    if (this.#state !== State.Started) {
      return;
    }
    this.#state = State.Paused;

    this.#pauseStart = Date.now();

    cancelAnimationFrame(this.#tickHandler);
    this.#tickHandler = -1;
  }

  resume() {
    if (this.#state !== State.Paused) {
      return;
    }
    this.#state = State.Started;

    this.#pauseTime += Date.now() - this.#pauseStart;
    this.#tick();
  }

  reset() {
    this.pause();

    this.#state = State.Initialized;

    this.#pauseStart = 0;
    this.#pauseTime = 0;

    this.#animations = new Set();
    this.#startTimes = new Map();
  }

  add(animation: Animation, startTime: number = Date.now()) {
    this.#animations.add(animation);
    this.#startTimes.set(animation, startTime);
  }
}

export class Animation {
  /**
   * 属性动画
   * @param object 
   * @param property 
   * @param startValue 
   * @param endValue 
   * @param duration 
   * @param timingFunction 
   */
  constructor(
    private object: any,
    private property: string,
    private startValue: number,
    private endValue: number,
    readonly duration: number,
    readonly delay: number,
    readonly timingFunction: (t: number) => number = v => v,
    readonly templateFunction: (v: number) => string = v => `${v}`,
  ) {

  }

  receiveTime(time: number) {
    const range = this.endValue - this.startValue;
    const progress = this.timingFunction(time / this.duration);
    this.object[this.property] = this.templateFunction(
      this.startValue + range * progress
    );
  }
}